/**
 * zoneland.net Inc.
 * Copyright (c) 2002-2012 All Rights Reserved.
 */
package net.zoneland.knowledge.filters;

import java.io.IOException;
import java.util.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import net.zoneland.knowledge.model.*;
import net.zoneland.knowledge.service.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;

import net.zoneland.knowledge.cache.RedisCacheUtil;
import net.zoneland.knowledge.constant.Constants;
import net.zoneland.knowledge.controller.bean.UomUserOrgQueryVo;
import net.zoneland.knowledge.enums.CacheEnum;
import net.zoneland.knowledge.enums.LoginLogFromEnum;
import net.zoneland.knowledge.model.security.OperationContext;
import net.zoneland.knowledge.model.security.OperationContextHolder;
import net.zoneland.knowledge.model.security.SecurityContextHolder;
import net.zoneland.knowledge.utils.GsonUtils;
import net.zoneland.knowledge.utils.JwtUtil;
import net.zoneland.knowledge.utils.LoginUtils;
import net.zoneland.knowledge.utils.SpringContextUtil;
import net.zoneland.knowledge.utils.UrlUtil;

/**
 * @version v1.0
 * @ClassName: SecurityFilter
 * @Description: 登录、单点安全验证过滤
 * @Author: Pbody
 * @Date: 2020/3/11 13:55
 */
public class SecurityFilter implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(SecurityFilter.class);
    private static final String LOGIN_URL = "/login.jsp";
    private static final String DENY_URL = "/deny.jsp";
    private ResourceService resourceService;

    private RoleService roleService;

    private RestTemplate restTemplate;

    private final String getUserUrl;

    private String accessToken;

    private boolean demo;

    private LoginLogService loginLogService;

    private AuthService authService;

    private UserService userService;

    private PartyService partyService;

    private PurviewService purviewService;

    /**
     *  构造.
     * @param resourceService
     * @param roleService
     * @param restTemplate
     * @param accessToken
     * @param getUserUrl
     * @param authService
     * @param userService
     */
    public SecurityFilter(final ResourceService resourceService, final RoleService roleService,
                          final RestTemplate restTemplate, final String accessToken, final String getUserUrl,
                          final AuthService authService,final UserService userService,
                          final PurviewService purviewService) {
        this.resourceService = resourceService;
        this.roleService = roleService;
        this.restTemplate = restTemplate;
        this.accessToken = accessToken;
        this.getUserUrl = getUserUrl;
        this.authService = authService;
        this.userService = userService;
        this.purviewService = purviewService;
    }


    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(final ServletRequest httpRequest, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException {
        if (httpRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) httpRequest;
            String url = UrlUtil.buildRequestUrl(request);
            if (UrlUtil.pathMatchesUrl(TokenFilter.ALLOWED_URL, url)) {
                chain.doFilter(request, response);
                return;
            }
            try {
                if (authService.isMock()) {
                    LoginUserPojo user = authService.getLoginUser(request);
                    initPartyInfo(user);
                    if (StringUtils.isNotBlank(user.getUserID())) {

                        login(request, user);
                        HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(request);
                        requestWrapper.addHeader(Constants.TOKEN_NAME,
                                                 OperationContextHolder.getPrincipal()
                                                     .getAttrMap()
                                                     .get(Constants.TOKEN_NAME)
                                                     .toString());
                        chain.doFilter(requestWrapper, response);
                        return;
                    }
                }
            } catch (Exception e) {
                LOGGER.error("mock login error", e);
            }
            OperationContext operationContext = (OperationContext) request.getSession()
                    .getAttribute(Constants.OPERATION_ATTR);
            if (operationContext == null || operationContext.getPrincipal() == null) {
                String userId = this.getRemoteUserId(request);
                if (StringUtils.isNotEmpty(userId)) {
                    LOGGER.info("OA单点账号：[{}]————进入身份认证", userId);
                    boolean flag = auth(userId, request);
                    if (!flag) {
                        RequestDispatcher rd = request.getRequestDispatcher(DENY_URL);
                        rd.forward(httpRequest, response);
                        return;
                    }
                } else {
                    if (request.getRequestURI().indexOf(LOGIN_URL) == -1) {
                        LOGGER.error("========SecurityFilter doFilter failed! getRemoteUserId is null!");
                        response.setContentType(TokenFilter.CONTENT_TYPE);
                        response.getWriter().write("{\"result\":\"" + ResponseResult.ACCESS_FAILED_RESULT + "\", \"message\":\"token认证失败！\"}");
                        response.getWriter().flush();
                        return;
                    }
                }
            } else {
                OperationContextHolder.setOperationContext(operationContext);
                String userId = this.getRemoteUserId(request);
                if (StringUtils.isNotEmpty(userId)) {
                    String osSessionId = (String) request.getSession().getAttribute(Constants.OASSO_SESSION_ID);
                    if (!userId.equals(osSessionId)) {
                        LOGGER.info("OA单点账号：[{}]——进入身份认证", userId);
                        SecurityContextHolder.clear();
                        boolean flag = auth(userId, request);
                        if (!flag) {
                            RequestDispatcher rd = request.getRequestDispatcher(DENY_URL);
                            rd.forward(httpRequest, response);
                            return;
                        }
                    }
                }
            }
        }
        chain.doFilter(httpRequest, response);
    }

    /**
     * 获取当前登录的用户uid.
     * @param request http请求
     * @return 用户uid
     */
    private String getRemoteUserId(final HttpServletRequest request) {
        final String remoteUserId = authService.getUserId(request);
        if (StringUtils.isNotBlank(remoteUserId)) {
            return remoteUserId;
        }

        try {
            LOGGER.info("URL = {}",  request.getRequestURI());
            if (!(request.getRequestURI().contains("/index/moatalk.htm")
                    || request.getRequestURI().contains("/meeting/signIn.htm"))) {
                return null;
            }
            final String mxSsoToken = request.getParameter("mx_sso_token");
            if (StringUtils.isBlank(mxSsoToken)) {
                return null;
            }
            //从minxing获取对应的人员信息
            final HttpHeaders headers = new HttpHeaders();
            headers.add("AUTHORIZATION", "Bearer " + accessToken);
            final HttpEntity<String> requestEntity = new HttpEntity<>(headers);
            final String url = getUserUrl + "/api/v1/oauth/user_info/" + mxSsoToken;
            final ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
            if (responseEntity.getStatusCode() != HttpStatus.OK) {
                return null;
            }
            final String body = responseEntity.getBody();
            if (StringUtils.isBlank(body)) {
                return null;
            }
            return Optional.ofNullable(JSON.parseObject(body)).map(it -> it.getString("login_name")).orElse(null);
        } catch (RestClientException e) {
            LOGGER.error("从minxing获取当前登录用户uid失败！", e);
        }
        return null;
    }

    @Override
    public void destroy() {
    }

    private boolean auth(final String userId, final HttpServletRequest request) {
        try {
            final LoginUserPojo user = userService.getUserByUid(userId);
            // 判断用户是否存在
            if (StringUtils.isNotEmpty(user.getUserID())) {
                initPartyInfo(user);
                login(request, user);
                LOGGER.info("用户【{}】在本系统中登录成功！", userId);
            } else {
                LOGGER.info("OA单点失败，用户【{}】在本系统中不存在", userId);
                return false;
            }
            return true;
        } catch (Exception e) {
            LOGGER.error("OA单点认证异常", e);
            return false;
        }
    }

    /**
     * 初始化党员相关信息.
     * @param user 当前登录用户.
     */
    private void initPartyInfo(final LoginUserPojo user) {
        final UomUserOrgQueryVo vo = new UomUserOrgQueryVo();
        // 用员工编号查可能会有问题，1、员工编号格式有多种E开始的或者纯数字，2，党员信息中有没有员工编号的数据
        // 目前看使用手机号比较合适
        vo.setMobile(user.getMobilePhone());
        vo.setIgnoreHiddenInfo(true);
        final List<PartyUser> partyUsers = partyService.searchByCondition(vo);
        if (CollectionUtils.isNotEmpty(partyUsers)) {
            final PartyUser partyUser = partyUsers.get(0);
            user.setPartyUser(partyUser);
            user.setGanShi(partyUser.getGanShi());
            user.setPartyMember(true);
            user.setPartyOrgName(partyUser.getOrgName());
            user.setPartyOrgUuid(partyUser.getOrgUUID());
        } else {
            user.setGanShi(false);
            user.setPartyMember(false);
        }
        user.setPurviewEnums(purviewService.getPurviewEnumsByUser(user));
    }

    private void login(final HttpServletRequest request, final LoginUserPojo user) {
        OperationContextHolder.createOperationContext(user.getUserID(), user.getCompanyDN());
        OperationContextHolder.getOperationContext().setAuthenticated(true);
        // 用户姓名
        OperationContextHolder.getPrincipal().setUserName(user.getUserName());
        // email
        OperationContextHolder.getPrincipal().setEmail(user.getEmail());
        OperationContextHolder.getPrincipal().setLogonId(user.getUserID());
        final String ip = LoginUtils.getIpAddr(request);
        OperationContextHolder.getPrincipal().setIp(ip);
        final Map<String, Object> attrMap = new HashMap<>();
        // 设置用户信息
        attrMap.put(Constants.SESSION_ATTRMAP_USER, user);
        //根据用户信息获取角色职务权限信息
        final List<Role> roleList = roleService.getRoleListByUser(user);
        //将角色信息放入session
        attrMap.put(Constants.SESSION_ATTRMAP_ROEL, roleList);
        //设置Token信息
        final String tokenId = UUID.randomUUID().toString();
        //生成签名
        final String tokenInfo = JwtUtil.sign(user.getUserID(), tokenId);
        final RedisCacheUtil redisCache = SpringContextUtil.getBean(RedisCacheUtil.class);
        //缓存设置 用户权限key，存入redis中的值，超时时长（单位小时）
        redisCache.set(CacheEnum.USER_ROLE_CACHE.getValue() + tokenId,
                GsonUtils.GSON.toJson(attrMap),
                Constants.USER_ROLE_CACHE_EXPIRE_HOURS);
        attrMap.put(Constants.TOKEN_NAME, tokenInfo);
        LOGGER.info(">>>>>>>>>>>>>token：[{}]", tokenInfo);

        // 设置登录Session信息
        OperationContextHolder.getPrincipal().setAttrMap(attrMap);
        request.getSession().setAttribute(Constants.OPERATION_ATTR,
                OperationContextHolder.getOperationContext());
        request.getSession().setAttribute(Constants.OASSO_SESSION_ID, user.getUserID());

        //构建并保存登录日志信息.
        buildAndSaveLoginLog(ip, user);
    }

    /**
     * 构建并保存登录日志信息.
     * @param ip 当前登录用户的ip.
     * @param user 当前登录用户信息.
     */
    private void buildAndSaveLoginLog(final String ip, final LoginUserPojo user){
        //如果是本地测试就不保存日志，默认false
        if (demo) {
            return;
        }
        final LoginLog log = new LoginLog();
        log.setClientIp(ip);
        log.setServerIp(LoginUtils.getServerIp());
        log.setUserId(user.getUserID());
        log.setOrgId(user.getDepartmentDN());
        log.setCompany(user.getCompanyName());
        log.setUserName(user.getUserName());
        log.setLoginFrom(LoginLogFromEnum.OA.getKey());
        loginLogService.save(log);
    }
    /**
     * .
     *
     * @return demo
     */
    public boolean isDemo() {
        return demo;
    }

    /**
     * .
     *
     * @param demo to set
     */
    public void setDemo(final boolean demo) {
        this.demo = demo;
    }

    /**
     * .
     *
     * @return loginLogService
     */
    public LoginLogService getLoginLogService() {
        return loginLogService;
    }

    /**
     * .
     *
     * @param loginLogService to set
     */
    public void setLoginLogService(final LoginLogService loginLogService) {
        this.loginLogService = loginLogService;
    }

    /**
     * 获取 partyService.
     *
     * @return partyService
     */
    public PartyService getPartyService() {

        return partyService;
    }

    /**
     * 设置 partyService.
     *
     * @param partyService partyService
     */
    public void setPartyService(final PartyService partyService) {

        this.partyService = partyService;
    }

}
